module Memory (

	input	clock ,
	input	reset ,
	
    // Execute
	input	[2:0]	ex_mem_msm ,
	input	[2:0]	ex_mem_msl ,
	input	ex_mem_readmem ,
	input	ex_mem_writemem ,
	input	ex_mem_mshw ,
	input	ex_mem_lshw ,
	input	[31:0]	ex_mem_regb ,
	input	[2:0]	ex_mem_selwsource ,
	input	[4:0]	ex_mem_regdest ,
	input	ex_mem_writereg ,
	input	[31:0]	ex_mem_aluout ,
	input	[31:0]	ex_mem_wbvalue ,
	
    // Memory Controller
	output	mem_mc_rw ,
	output	mem_mc_en ,
	output	[31:0]	mem_mc_addr ,
	inout	[31:0]	mem_mc_data ,
    output  mem_mc_en1h,
    output  mem_mc_en1l,
    output  mem_mc_en2h,
    output  mem_mc_en2l,
	
    // Forwarding
	output	[31:0] mem_fw_wbvalue ,
	output	mem_fw_writereg ,
	
    //  Writeback
	output reg	[4:0]	mem_wb_regdest ,
	output reg	mem_wb_writereg ,
	output reg	[31:0]	mem_wb_wbvalue ) ;

    // Auxiliary registers
    reg mem_mc_en1h_reg;
    reg mem_mc_en1l_reg;
    reg mem_mc_en2h_reg;
    reg mem_mc_en2l_reg;
    reg [31:0] mem_mc_data_reg;

    wire [31:0] mux_loadregb;
    reg [31:0] loadval;
    wire [31:0] mux_ldrbexval;
  
    // load/store signal coding
    always @(posedge(clock)) begin

        if (ex_mem_mshw && ex_mem_lshw && ex_mem_readmem && !ex_mem_writemem && (ex_mem_msm == 3) && (ex_mem_msl == 3)) begin
            // LB
            loadval <= {{24{mem_mc_data[31]}},mem_mc_data[7:0]};
            mem_mc_en1h_reg <= 1'b1;
            mem_mc_en1l_reg <= 1'b1;
            mem_mc_en2h_reg <= 1'b1;
            mem_mc_en2l_reg <= 1'b1;
        end

        else if (ex_mem_mshw && ex_mem_lshw && ex_mem_readmem && !ex_mem_writemem && (ex_mem_msm == 3) && (ex_mem_msl == 1)) begin
            // LH
            loadval <= {{16{mem_mc_data[31]}},mem_mc_data[15:0]};
            mem_mc_en1h_reg <= 1'b1;
            mem_mc_en1l_reg <= 1'b1;
            mem_mc_en2h_reg <= 1'b1;
            mem_mc_en2l_reg <= 1'b1;
        end

        
        else if (ex_mem_mshw && !ex_mem_lshw && ex_mem_readmem && !ex_mem_writemem && (ex_mem_msm == 2) && (ex_mem_msl == 0)) begin
        
            // LWL
            if (ex_mem_aluout[1:0] == 0) begin
                loadval <= mem_mc_data;
                mem_mc_en1h_reg <= 1'b1;
                mem_mc_en1l_reg <= 1'b1;
                mem_mc_en2h_reg <= 1'b1;
                mem_mc_en2l_reg <= 1'b1;
            end
            else if (ex_mem_aluout[1:0] == 1) begin
                loadval = {mem_mc_data[23:0],ex_mem_regb[7:0]};
                mem_mc_en1h_reg <= 1'b1;
                mem_mc_en1l_reg <= 1'b1;
                mem_mc_en2h_reg <= 1'b1;
                mem_mc_en2l_reg <= 1'b0;
            end
            else if (ex_mem_aluout[1:0] == 2) begin
                loadval <= {mem_mc_data[15:0],ex_mem_regb[15:0]};
                mem_mc_en1h_reg <= 1'b1;
                mem_mc_en1l_reg <= 1'b1;
                mem_mc_en2h_reg <= 1'b0;
                mem_mc_en2l_reg <= 1'b0;
            end
            else if (ex_mem_aluout[1:0] == 3) begin
                loadval <= {mem_mc_data[7:0],ex_mem_regb[23:0]};
                mem_mc_en1h_reg <= 1'b1;
                mem_mc_en1l_reg <= 1'b0;
                mem_mc_en2h_reg <= 1'b0;
                mem_mc_en2l_reg <= 1'b0;
            end

        end

        else if (ex_mem_mshw && ex_mem_lshw && ex_mem_readmem && !ex_mem_writemem && (ex_mem_msm == 1) && (ex_mem_msl == 2)) begin
            // LW
            loadval <= mem_mc_data;
            mem_mc_en1h_reg <= 1'b1;
            mem_mc_en1l_reg <= 1'b1;
            mem_mc_en2h_reg <= 1'b1;
            mem_mc_en2l_reg <= 1'b1;
        end

        else if (ex_mem_mshw && ex_mem_lshw && ex_mem_readmem && !ex_mem_writemem && (ex_mem_msm == 0) && (ex_mem_msl == 4)) begin
            loadval <= {ex_mem_regb[31:8],mem_mc_data[7:0]};
            // LBU
            mem_mc_en1h_reg <= 1'b1;
            mem_mc_en1l_reg <= 1'b1;
            mem_mc_en2h_reg <= 1'b1;
            mem_mc_en2l_reg <= 1'b1;
        end

        else if (ex_mem_mshw && ex_mem_lshw && ex_mem_readmem && !ex_mem_writemem && (ex_mem_msm == 0) && (ex_mem_msl == 1)) begin
            // LHU
            loadval <= {ex_mem_regb[31:16],mem_mc_data[15:0]};
            mem_mc_en1h_reg <= 1'b1;
            mem_mc_en1l_reg <= 1'b1;
            mem_mc_en2h_reg <= 1'b1;
            mem_mc_en2l_reg <= 1'b1;
        end


        else if (!ex_mem_mshw && ex_mem_lshw && ex_mem_readmem && !ex_mem_writemem && (ex_mem_msm == 0) && (ex_mem_msl == 1)) begin
        
            // LWR
            if (ex_mem_aluout[1:0] == 0) begin
                loadval <= {ex_mem_regb[31:8],mem_mc_data[31:24]};
                mem_mc_en1h_reg <= 1'b0;
                mem_mc_en1l_reg <= 1'b0;
                mem_mc_en2h_reg <= 1'b0;
                mem_mc_en2l_reg <= 1'b1;
            end
            else if (ex_mem_aluout[1:0] == 1) begin
                loadval <= {ex_mem_regb[31:16],mem_mc_data[31:16]};
                mem_mc_en1h_reg <= 1'b0;
                mem_mc_en1l_reg <= 1'b0;
                mem_mc_en2h_reg <= 1'b1;
                mem_mc_en2l_reg <= 1'b1;
            end
            else if (ex_mem_aluout[1:0] == 2) begin
                loadval <= {ex_mem_regb[31:24],mem_mc_data[31:8]};
                mem_mc_en1h_reg <= 1'b0;
                mem_mc_en1l_reg <= 1'b1;
                mem_mc_en2h_reg <= 1'b1;
                mem_mc_en2l_reg <= 1'b1;
            end
            else if (ex_mem_aluout[1:0] == 3) begin
                loadval <= mem_mc_data;
                mem_mc_en1h_reg <= 1'b1;
                mem_mc_en1l_reg <= 1'b1;
                mem_mc_en2h_reg <= 1'b1;
                mem_mc_en2l_reg <= 1'b1;
            end

        end
        
        else if (ex_mem_mshw && ex_mem_lshw && !ex_mem_readmem && ex_mem_writemem && (ex_mem_msm == 4) && (ex_mem_msl == 3)) begin
            // SB
            mem_mc_data_reg <= {24'bx,ex_mem_regb[7:0]};
            mem_mc_en1h_reg <= 1'b0;
            mem_mc_en1l_reg <= 1'b0;
            mem_mc_en2h_reg <= 1'b0;
            mem_mc_en2l_reg <= 1'b1;
        end

        else if (ex_mem_mshw && ex_mem_lshw && !ex_mem_readmem && ex_mem_writemem && (ex_mem_msm == 2) && (ex_mem_msl == 0)) begin
            // SH
            mem_mc_data_reg <= {{16{1'bx}},ex_mem_regb[15:0]};
            mem_mc_en1h_reg <= 1'b0;
            mem_mc_en1l_reg <= 1'b0;
            mem_mc_en2h_reg <= 1'b1;
            mem_mc_en2l_reg <= 1'b1;
        end

        else if (!ex_mem_mshw && ex_mem_lshw && !ex_mem_readmem && ex_mem_writemem && (ex_mem_msm == 0) && (ex_mem_msl == 1)) begin
        
            // SWL
            if (ex_mem_aluout[1:0] == 0) begin
                mem_mc_data_reg <= ex_mem_regb;
                mem_mc_en1h_reg <= 1'b1;
                mem_mc_en1l_reg <= 1'b1;
                mem_mc_en2h_reg <= 1'b1;
                mem_mc_en2l_reg <= 1'b1;
            end
            else if (ex_mem_aluout[1:0] == 1) begin
                mem_mc_data_reg <= {{8{1'bx}},ex_mem_regb[31:8]};
                mem_mc_en1h_reg <= 1'b0;
                mem_mc_en1l_reg <= 1'b1;
                mem_mc_en2h_reg <= 1'b1;
                mem_mc_en2l_reg <= 1'b1;
            end
            else if (ex_mem_aluout[1:0] == 2) begin
                mem_mc_data_reg <= {{16{1'bx}},ex_mem_regb[31:16]};
                mem_mc_en1h_reg <= 1'b0;
                mem_mc_en1l_reg <= 1'b0;
                mem_mc_en2h_reg <= 1'b1;
                mem_mc_en2l_reg <= 1'b1;
            end
            else if (ex_mem_aluout[1:0] == 3) begin
                mem_mc_data_reg <= {{24{1'bx}},ex_mem_regb[31:24]};
                mem_mc_en1h_reg <= 1'b0;
                mem_mc_en1l_reg <= 1'b0;
                mem_mc_en2h_reg <= 1'b0;
                mem_mc_en2l_reg <= 1'b1;
            end
        end

        else if (ex_mem_mshw && ex_mem_lshw && !ex_mem_readmem && ex_mem_writemem && (ex_mem_msm == 1) && (ex_mem_msl == 2)) begin
            // SW
            mem_mc_data_reg <= ex_mem_regb;
            mem_mc_en1h_reg <= 1'b1;
            mem_mc_en1l_reg <= 1'b1;
            mem_mc_en2h_reg <= 1'b1;
            mem_mc_en2l_reg <= 1'b1;
        end


        else if (ex_mem_mshw && !ex_mem_lshw && !ex_mem_readmem && ex_mem_writemem && (ex_mem_msm == 2) && (ex_mem_msl == 0)) begin
        
            // SWR
            if (ex_mem_aluout[1:0] == 0) begin
                mem_mc_data_reg <= {ex_mem_regb[7:0],{24{1'bx}}};
                mem_mc_en1h_reg <= 1'b1;
                mem_mc_en1l_reg <= 1'b0;
                mem_mc_en2h_reg <= 1'b0;
                mem_mc_en2l_reg <= 1'b0;
            end
            else if (ex_mem_aluout[1:0] == 1) begin
                mem_mc_data_reg <= {ex_mem_regb[15:0],{16{1'bx}}};
                mem_mc_en1h_reg <= 1'b1;
                mem_mc_en1l_reg <= 1'b1;
                mem_mc_en2h_reg <= 1'b0;
                mem_mc_en2l_reg <= 1'b0;
            end
            else if (ex_mem_aluout[1:0] == 2) begin
                mem_mc_data_reg <= {ex_mem_regb[23:0],{8{1'bx}}};
                mem_mc_en1h_reg <= 1'b1;
                mem_mc_en1l_reg <= 1'b1;
                mem_mc_en2h_reg <= 1'b1;
                mem_mc_en2l_reg <= 1'b0;
            end
            else if (ex_mem_aluout[1:0] == 3) begin
                mem_mc_data_reg <= ex_mem_regb;
                mem_mc_en1h_reg <= 1'b1;
                mem_mc_en1l_reg <= 1'b1;
                mem_mc_en2h_reg <= 1'b1;
                mem_mc_en2l_reg <= 1'b1;
            end

        end

        else if (ex_mem_mshw && ex_mem_lshw && ex_mem_readmem && !ex_mem_writemem && (ex_mem_msm == 1) && (ex_mem_msl == 2)) begin
            // LL
            loadval <= mem_mc_data;
            mem_mc_en1h_reg <= 1'b1;
            mem_mc_en1l_reg <= 1'b1;
            mem_mc_en2h_reg <= 1'b1;
            mem_mc_en2l_reg <= 1'b1;
        end

        else if (ex_mem_mshw && ex_mem_lshw && !ex_mem_readmem && ex_mem_writemem && (ex_mem_msm == 1) && (ex_mem_msl == 2)) begin
            // SC
            mem_mc_data_reg <= ex_mem_regb;
            mem_mc_en1h_reg <= 1'b1;
            mem_mc_en1l_reg <= 1'b1;
            mem_mc_en2h_reg <= 1'b1;
            mem_mc_en2l_reg <= 1'b1;
        end

    end

    // Enable signals
    assign  mem_mc_en1h = mem_mc_en1h_reg;
    assign  mem_mc_en1l = mem_mc_en1l_reg;
    assign  mem_mc_en2h = mem_mc_en2h_reg;
    assign  mem_mc_en2l = mem_mc_en2l_reg;

    //Forwarded signals
    assign mem_mc_addr = ex_mem_aluout;
    assign mem_fw_wbvalue = (ex_mem_writemem) ? 32'bz : mux_ldrbexval;
    assign mem_fw_writereg = (ex_mem_mshw && ex_mem_lshw && !ex_mem_readmem && ex_mem_writemem && (ex_mem_msm==1) && (ex_mem_msl==2)) ? 1'b1 : ex_mem_writereg;
    
    //Memory access signals
    assign mem_mc_rw = (~ex_mem_readmem & ex_mem_writemem);
    assign mem_mc_en = (ex_mem_readmem | ex_mem_writemem);

    //mem_mc_data
    assign mem_mc_data = (ex_mem_writemem) ? mem_mc_data_reg : 32'bz;

    //mux_loadregb
    assign mux_loadregb = (ex_mem_readmem && !ex_mem_writemem) ? loadval : ex_mem_regb;

    //mux_ldrbexval
    assign mux_ldrbexval = (~ex_mem_selwsource[2] & ~ex_mem_selwsource[1] & ex_mem_selwsource[0]) ? mux_loadregb : ex_mem_wbvalue;

	always @(negedge(reset)) begin
	    mem_wb_regdest <= 5'b0;
	    mem_wb_writereg <= 1'b0;
	    mem_wb_wbvalue <= 32'b0;
	end

	always @(negedge(clock)) begin
        
        //mem_wb_wbvalue
        mem_wb_regdest <= ex_mem_regdest;
        
        // Instruction is SC
        if (ex_mem_mshw && ex_mem_lshw && !ex_mem_readmem && ex_mem_writemem && (ex_mem_msm==1) && (ex_mem_msl==2)) begin
            mem_wb_wbvalue <= 32'b1;
            mem_wb_writereg <= 1'b1;
            mem_wb_wbvalue <= 32'bz;
        end else begin
            if(!ex_mem_writemem) begin
                mem_wb_wbvalue <= mux_ldrbexval;
            end else begin
                mem_wb_wbvalue <= 32'bz;
            end
    	    mem_wb_writereg <= ex_mem_writereg;
        end

	end

endmodule
